""" Defines the FieldSet model and related functions

A FieldSet instance contains an ordered collection of fields. We can
use those fields to dynamically create a new subclass of db.Model
and typecast ValueSet objects
"""

from google.appengine.ext import db
from app.helpers.memoize import method_memoize
from app.helpers.pickling import PickleProperty
from valuesets import ValueSet
import md5
import new
import re


ATT_NAME_REGEX = re.compile('^[^a-zA-Z]*[0-9]+|[^a-zA-Z0-9]+')
def _convert_to_att_name(name):
  """ Converts random string to something acceptable to Python / AppEngine
  
  No leading digits allowed in name
  >>> _convert_to_att_name('3things2do')
  'things2do'
  
  No beginning or ending names with underscores
  >>> _convert_to_att_name('__builtin__')
  'builtin'
  
  All characters are either underscores or alphanumeric
  >>> _convert_to_att_name('fun@99__penguins!')
  'fun_99_penguins'
  
  Everything is lowercase
  >>> _convert_to_att_name('ROFLCOPTER')
  'roflcopter'
  
  """
  name = str(name)
  return ATT_NAME_REGEX.sub('_', name).strip('_').lower()


class FieldSet(db.Model):
  """ A FieldSet is a grouping of properties that apply to some object (person)
  
  A FieldSet instance stores data, via pickling, on how to interpret the data
  stored in a ValueSet. This allows us to dynamically create subclasses of
  ValueSet based on data within this field
  
  """
  # Which dataset this belongs to
  dataset_key = db.Property()
  
  # The verbose name of this fieldset, as displayed to end user
  verbose_name = db.StringProperty()
    
  # The properties for this fieldset, should be a list of db.Property objects
  # Recommended that name and/or verbose_names be set on each property
  fields_list = PickleProperty()
  
  # How many entities with this fieldset?
  count = db.IntegerProperty(default=0)
  
  @property
  @method_memoize
  def model(self):
    """ Dynamically creates ValueSet subclasses based on self.fields_list """
    classobj_dict = {
      '_field_order' : self.fields_list,
      '_default_dataset_key' : self.dataset_key,
      '_default_fieldset_key' : self.key(),
      'fieldset' : self,
    }
    
    for field in self.fields_list:
      classobj_dict[field.name] = field
    
    classobj = new.classobj(
      str(self.key().name()).capitalize(), (ValueSet,), classobj_dict)
          
    return classobj
  
  @classmethod
  def incr_count(cls, fieldset_key, count):
    """ Adjust counts transactionally """
    def update():
      fieldset = cls.get(fieldset_key)
      fieldset.count += count
      fieldset.put()
    
    db.run_in_transaction(update)
    
  @classmethod
  def create(cls, dataset_key, verbose_name, *fields):
    """ Should use to create fields -- names them properly """
    # The naming of things, it must be done
    key_name = cls.get_key(dataset_key, verbose_name)
    for index, field in enumerate(fields):
      if field.name:
        field.name = _convert_to_att_name(field.name)
      elif field.verbose_name:
        field.name = _convert_to_att_name(field.verbose_name)      
      if not field.name or field.name in ValueSet.properties().keys():
        field.name = 'field_%s' % index
    
    # Check for duplicates before inserting
    f = cls.get_or_insert(key_name=key_name,
      verbose_name=verbose_name,
      dataset_key=dataset_key,
      fields_list=fields)
    f.put()
    return f
  
  @classmethod
  def get_key(cls, dataset_key, verbose_name):
    return 'k' + \
      md5.new('_'.join([dataset_key.name(), verbose_name])).hexdigest()

from datasets import DEFAULT_DATASET_KEY
from app.helpers import properties as props
DEFAULT_PRIMARY_FIELDSET_NAME = 'Name & Basic Contact Info'
DEFAULT_PRIMARY_FIELDSET = FieldSet.create(DEFAULT_DATASET_KEY,
  DEFAULT_PRIMARY_FIELDSET_NAME,
  props.NameProperty(verbose_name='Last Name, First Name'),
  props.EmailProperty(verbose_name='E-mail Address'),
  props.PhoneNumberProperty(verbose_name='Phone Number'))
